package otxImporter;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Vector;

/**
 * Simple class that importa an image into an array of Objects satisfying the OBX format
 * 
 * @author ziotob
 * @version 1.0
 */
public class OTXImporter {

	/********************************************
	 * !!!!!!!!!OBX FORMAT DECLARATION!!!!!!!!! *
	 ******************************************** 
	 * 
	 * 0----------+ 0 = id (first pixel) |imageimage| |-+ = borders |imageimage| |imageimage| |imageimage| |imageimage|
	 * +----------+
	 * 
	 * - IDENTIFIER: The format itentifies itself just looking at the first pixel (position 0,0) and comparing it with
	 * the identifier color
	 * 
	 * - BORDER: The border (drawn outside) of each image is composed by a sequence of *borderColor*. Example: Image
	 * start x : 100, 100 Image size : 50, 70 Border Positions: Top: x: 99 y: 99 -> 151 Right: x: 99 -> 171 y: 151
	 * Bottom: x: 171 y: 151 -> 99 Left: x: 171 -> 99 y: 99
	 * 
	 * - ID: The top-left pixel of each border identifies the id of the object. The status can also be saved in the
	 * alpha channel or in the last two HEX values (B or ALPHA)
	 * 
	 */

	/**********************************************
	 * !!!!!!!!COSTOMIZABLE VARIABLES!!!!!!!!!!!! *
	 ********************************************** 
	 */

	/**
	 * Determines if the status of image has to be read from alpha channel (true value) or from the Blue channel (false)
	 */
	public static boolean			alphaActive		= true;

	/**
	 * Defines if the importation process should use multiple threads or not
	 */
	private boolean					multiThread		= true;

	/**
	 * Color that identifies that the image is a OTX object
	 */
	public static Color				identifierColor	= new Color(100, 100, 100);

	/**
	 * Color that identifies the border of the object
	 */
	public static Color				borderColor		= new Color(255, 0, 255);

	/****************************************
	 * !!!!!!!!!!CLASS VARIABLES!!!!!!!!!!! *
	 **************************************** 
	 */

	/**
	 * Image to scan
	 */
	private BufferedImage			img;

	/**
	 * Height of the given image
	 */
	private int						height;

	/**
	 * Width of the given image
	 */
	private int						width;

	/**
	 * Matrix of check-bits for the image (every thread that looks at the bit sets it to false)
	 */
	private boolean[][]				checkBits;

	/**
	 * The last shown bit (X value)
	 */
	private int						positionX		= 0;

	/**
	 * The last shown bit (Y value)
	 */
	private int						positionY		= 0;

	/**
	 * Threads started
	 */
	private int						startedThreads	= 0;

	/**
	 * Threads stopped
	 */
	private int						stoppedThreads	= 0;

	/**
	 * Imported objects
	 */
	private Vector<OTXImportable>	objects;

	/*********************
	 * !!!!!METHODS!!!!! *
	 ********************* 
	 */

	/**
	 * Constructor instantiates the class from the BufferedImage object
	 * 
	 * @param img
	 *            BufferedImage image to be checked
	 */
	public OTXImporter(BufferedImage img) {
		this.img = img;
		this.height = img.getHeight();
		this.width = img.getWidth();
		this.checkBits = new boolean[this.height][this.width];
		this.objects = new Vector<OTXImportable>();
		for (int i = 0; i < this.height; i++) {
			Arrays.fill(this.checkBits[i], Boolean.TRUE);
		}
	}

	/**
	 * Checks the image and if it has the requested requirements returns true. Otherwise returns false
	 * 
	 * @param b
	 *            BufferedImage The image to check
	 * @return boolean
	 */
	public static boolean isOTX(BufferedImage b) {
		Color c = new Color(b.getRGB(0, 0));
		return c.equals(identifierColor);
	}

	/**
	 * Returns an array of OTXImportable objects
	 * 
	 * @return Vector<OTXImportable>
	 */
	public Vector<OTXImportable> getObjects() {
		this.startThread();

		while (this.startedThreads > this.stoppedThreads) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
			}
		}

		return this.objects;
	}

	/**
	 * Returns the x and y of the next point to be checked
	 * 
	 * @return int[2]
	 */
	public synchronized int[] getNextPoint() {
		if ((this.positionX >= this.width) || (this.positionY >= this.height)) {
			return null;
		}

		while ((this.positionY < this.height) || (this.positionX < this.width)) {
			if (this.checkBits[this.positionX][this.positionY]) {
				break;
			}

			this.positionX++;
			if (this.positionX >= this.width) {
				this.positionX = 0;
				this.positionY++;
				if (this.positionY >= this.height) {
					break;
				}
			}
		}

		if ((this.positionX >= this.width) || (this.positionY >= this.height)) {
			return null;
		}

		this.setPointChecked(this.positionX, this.positionY);

		return new int[] { this.positionX, this.positionY };
	}

	/**
	 * Sets a specific point as checked
	 * 
	 * @param x
	 *            Position of the point
	 * @param y
	 *            Position of the point
	 */
	public synchronized void setPointChecked(int x, int y) {
		this.checkBits[x][y] = false;
	}

	/**
	 * Sets a range of points as checked
	 * 
	 * @param x
	 *            Position of the point
	 * @param y
	 *            Position of the point
	 */
	public synchronized void setPointChecked(int startX, int endX, int y) {
		while (startX <= endX) {
			this.checkBits[startX][y] = false;
			startX++;
		}
	}

	/**
	 * Starts a new thread if multiThreading enabled or if it's the first started thread
	 * 
	 * @return boolean
	 */
	public boolean startThread() {
		if (this.multiThread || (this.startedThreads == 0)) {
			OTXObjectThread t = new OTXObjectThread(this);
			t.start();
			this.startedThreads++;
			return true;
		}
		return false;
	}

	/**
	 * Stops a thread and adds it to the counter
	 */
	public synchronized void setStopped() {
		this.stoppedThreads += 1;
		//		if(this.stoppedThreads == this.startedThreads)
		//			System.out.println("Importer finished");
	}

	/**
	 * Returns the image
	 * 
	 * @return BufferedImage
	 */
	public BufferedImage getImage() {
		return this.img;
	}

	public int[] getIdAndState(Color c) {
		if (alphaActive) {
			return new int[] { (int) ((c.getRed() * Math.pow(256, 2)) + (c.getGreen() * Math.pow(256, 1)) + c.getBlue()), c.getAlpha() };
		} else {
			return new int[] { (int) ((c.getRed() * Math.pow(256, 1)) + c.getGreen()), c.getBlue() };
		}
	}

	/**
	 * Adds an object to the array
	 * 
	 * @param o
	 *            Object
	 */
	public synchronized void addObject(OTXImportable o) {

		boolean found = false;

		for (OTXImportable obj : this.objects) {
			if (obj.equals(o)) {
				obj.addState(o);
				found = true;
				break;
			}
		}

		if (!found) {
			this.objects.add(o);
		}
	}

}
